<?php
namespace org\crawlgenie\application\document;

use \Logger;
use \CGException;
/**
 * Document class is an abstract base class that is meant to have the appropriate
 * subclass instantiated via the main Factory class. Views can be different
 * kinds, HTML, XML or JSON.
 *
 * Document sub-classes are composite classes that contains all data models
 * as well as any dynamic variables to be used for rendering by appropriate
 * renderer objects.
 *
 *  @author $Author: jinsonxu@gmail.com $:
 *  $Date: 2014-11-09 22:57:19 +0000 (Sun, 09 Nov 2014) $:
 *  $Id: Document.php 6 2014-11-09 22:57:19Z jinsonxu@gmail.com $:
 *  @version $Rev: 6 $:
 *  @package aegenie.application
 *  @subpackage document
 */


abstract class Document {

    protected static $_instance = null;
    protected $_renderer;
    
    protected $_modelArray ;
    protected $_dynamicVarArray;

    protected $_toRenderArray;



    /**
     * Constructor for Document instance with an optional Renderer object injected in
     * @param IRenderable $renderer
     */
    public function __construct(IRenderable $renderer = null) {
        $this->_renderer = ($renderer !== null) ? $renderer : null;
        $this->_modelArray = array();
        $this->_dynamicVarArray = array();
        $this->_toRenderArray = array();
    }


    /**
     * Static instantiation method for obtaining a single concrete Document instance
     * @static
     * @return  Document    An instance of a concrete implementation of the Document class
     */
    abstract public static function getInstance();


    /**
     *  Construction method template
     */
    public final function prepareDocument() {
        $this->preProcess();

        $this->_renderer->setModels($this->_modelArray, $this->_dynamicVarArray);
        $this->setHeader();
        $this->parsePage();
        $this->sendDocument();
        
        $this->postProcess();
    }


    public abstract function setHeader();
    public abstract function parsePage();
    public abstract function sendDocument();



    /**
     * Pre processing method for over-riding by concrete subclasses
     * Provides a way to hook custom methods into the pre Document preparation flow
     */
    protected function preProcess() {

    }


    /**
     * Post-processing method for over-riding by concrete subclasses
     * Provides a way to hook custom methods into the post Document preparation flow
     */
    protected function postProcess() {

    }

    /**
     * Static method to reset the stored static singleton instance of Document
     * @static
     */
    public static function reset() {
        self::$_instance = null;
    }


    /**
     * Method to inject a renderer for this document
     * @param   <IRenderable>  $renderer  Renderer for this document
     */
    public function setRenderer(IRenderable $renderer) {
        $this->_renderer = $renderer;
    }


    /**
     * Method to save a model into the stored model array.
     * @param   IModel  $model  An object that implements the IModel interface
     * @param   string  $ident  Identifier of this model object
     */
    public function setModel($ident, IModel $model) {
        $this->_modelArray[$ident] = $model;
    }


    /**
     * Method to get a model in the stored model array.
     * @param   string  $ident  Identifier of the model object to get
     * @exception   CGException
     * @return  IModel  The model object stored with the specific ident
     */
    public function getModel($ident) {
        if(!array_key_exists($ident, $this->_modelArray)) {
            throw new CGException('Attempt to access non-existent model identified by '.$ident.' in Document');
        }
        return $this->_modelArray[$ident];
    }



    /**
     * Method to save a variable into this object
     * @param   IModel  $var  A variable to dynamically inject into the Document object
     * @param   string  $ident  Identifier of this var
     */
    public function setVar($ident, $var) {
        $this->_dynamicVarArray[$ident] = $var;
    }


    /**
     * Method to get a variable from the dynamic variable array
     * @param   string  $ident  Identifier of the var to get
     * @exception   GWTException
     * @return  mixed   The dynamically stored variable
     */
    public function getVar($ident) {
        if(!array_key_exists($ident, $this->_dynamicVarArray)) {
            throw new CGException('Attempt to access non-existent dynamic variable identified by '.$ident.' in Document');
        }
        return $this->_dynamicVarArray[$ident];
    }




    /**
     * Method to record which variable or model ident to render
     * @param   string  $ident  Identifier of the type of data to render
     * @param   string  $type   The type of data to render
     */
    public function addToRenderArray($type, $ident) {
        if(!isset($this->_toRenderArray[$type]) || @array_search($ident, $this->_toRenderArray[$type]) === FALSE) {
            $this->_toRenderArray[$type][] = $ident;
        } else {
            new CGException('Unable to add data of type ['.$type.'] and ident ['.$ident.']. Record already exists in toRenderArray');
        }
    }


    /**
     * Method to remove a particular record from the toRender array
     * @param   string  $ident  Identifier of the type of data to render
     * @param   string  $type   The type of data to render
     */
    public function removeFromRenderArray($type, $ident) {
        $keysToRemove = array_keys($this->_toRenderArray[$type], $ident);
        foreach($keysToRemove as $key) {
            unset($this->_toRenderArray[$type][$key]);
        }
    }

    /**
     * Method to get combined array of all models or variables to be rendered
     * @return  mixed   The array of data to be rendered
     * TODO: Write a check for valid types. Do the same in the adding and removal methods
     */
    public function getToRenderArray() {
        $resultsArray = array();
        $keys = array_keys($this->_toRenderArray);
        foreach($keys as $type) {
            foreach($this->_toRenderArray[$type] as $value) {
                $resultsArray[$value] = ($type == 'model') ? $this->getModel($value) : $this->getVar($value);
            }
        }
        return $resultsArray;
    }

}
?>
